package IC.SymbolTable;

import java.util.*;

import IC.SemanticCheckes.SemanticError;

public class ClassSymbolTable extends SymbolTable {

	public ClassSymbolTable(String name, GlobalSymbolTable global){
		String superClassName = global.getClassSymbol(name).getIcClass().getSuperClassName();
		if (superClassName == null){
			this.InitSymbol(global);
			this.mySymbol = global.getClassSymbol(name);
			this.hasSuper = false;			
		}else{
			this.InitSymbol(global.getClassSymbolTable(superClassName));
			this.mySymbol = global.getClassSymbol(name);
			this.hasSuper = true;			
		}

	}
	
	public ClassSymbolTable getClassSymbolTable(String name){
		ClassSymbolTable csm = classSymbolTables.get(name);
		if (csm != null) {
			return csm;
		}
		else {
			for (ClassSymbolTable cst: classSymbolTables.values()){
				csm = cst.getClassSymbolTable(name);
				if (csm != null) {
					return csm;
				}
			}
		}
		return null;
	}

	public MethodSymbol getMethodSymbol(String name) throws SemanticError{
		MethodSymbol ms = methodSymbols.get(name);
		if (ms == null) {
			if (hasSuper){
				try{
					ms = ((ClassSymbolTable) parent).getMethodSymbol(name);	
					ms.isInherited = true;
				}catch (SemanticError e){
					throw new SemanticError("method does not exist in "+this.mySymbol.getName(), name);
				}
			} else {
				throw new SemanticError("method does not exist",name);
			}
		}
		return ms;
	}

	public VarSymbol getFieldSymbol(String name) throws SemanticError{
		VarSymbol fs = fieldSymbols.get(name);
		if (fs == null) {
			if (hasSuper){
				try{
					fs = ((ClassSymbolTable) parent).getFieldSymbol(name);
					fs.isDefined = true;
				}catch (SemanticError e){
					throw new SemanticError("field does not exist in "+this.mySymbol.getName(),name);
				}
			} else {
				throw new SemanticError("name cannot be resolved",name);
			}
		}
		return fs;
	}
	
	public ClassSymbol getMySymbol(){
		return this.mySymbol;
	}
	
	public MethodSymbolTable getMethodSymbolTable(String name){
		return methodSymbolTables.get(name);
	}

	public void addMethodSymbol(String name, MethodSymbol ms){
		this.methodSymbols.put(name, ms);
	}

	public void addMethodSymbolTable(MethodSymbolTable mst){
		methodSymbolTables.put(mst.getName(), mst);
	}

	public void addFieldSymbol(String name, String typeName) throws SemanticError{
		this.fieldSymbols.put(name,new VarSymbol(name, "FIELD", typeName,0));
	}

	public void addClassSymbolTable(ClassSymbolTable cst){
		classSymbolTables.put(cst.getMySymbol().getName(), cst);
	}
	

	public boolean hasSuperClass(){
		return this.hasSuper;
	}
	
	public String toString(){
		String str = "Class Symbol Table: "+this.mySymbol.getName();
		
		// print list of symbols (fields and methods)
		for(VarSymbol fs: fieldSymbols.values()){
			str += "\n\tField: "+fs.getType().getName()+" "+fs.getName();
		}
		
		for(MethodSymbol ms: methodSymbols.values()){
			String mType = (ms.isStatic()?"Static":"Virtual")+" method";
			str += "\n\t"+mType+": "+ms.getName()+" "+ms.getType().toString();
		}

		// print list of children tables (classes and methods)
		if(!classSymbolTables.isEmpty() || !methodSymbolTables.isEmpty()){
			str += "\nChildren tables: ";
			
			for(MethodSymbolTable mst: methodSymbolTables.values()){
				str += mst.getName()+", ";
			}
			for(ClassSymbolTable cst: classSymbolTables.values()){
				str += cst.getMySymbol().getName()+", ";
			}
			
			str = str.substring(0, str.length()-2);
		}
		str += "\n\n";
		
		// recursively print method symbol tables
		for(MethodSymbolTable mst: methodSymbolTables.values()){
			str += mst;
		}
		for(ClassSymbolTable cst: classSymbolTables.values()){
			str += cst;
		}
		
		return str;
	}
	
	public int getFieldDepthRec(String name){
		int fd;
		if(fieldSymbols.containsKey(name)){
			fd = this.getDepth(); 
		} else {
			if (hasSuper){
				fd = ((ClassSymbolTable) parent).getFieldDepthRec(name); 
			} else { //error
				System.err.println("*** BUG: tried to fetch depth for a non existing field: "+name);
				fd = -2;
			}
		}
		
		return fd;
	}
	
	private Map<String,VarSymbol> fieldSymbols = new HashMap<String,VarSymbol>();
	private Map<String,MethodSymbol> methodSymbols = new HashMap<String,MethodSymbol>();
	private Map<String,ClassSymbolTable> classSymbolTables = new HashMap<String,ClassSymbolTable>();
	private Map<String,MethodSymbolTable> methodSymbolTables = new HashMap<String,MethodSymbolTable>();
		
	private ClassSymbol mySymbol;
	private boolean hasSuper;	
}
